package com.zs.backend.common.utils;


import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

@Slf4j
public class BeanUtil {


    /**
     * 排序方式
     */
    public static final String SORT_DESC = "desc";

    /**
     * 统计不为空的属性
     *
     * @param obj 统计对象
     * @return 结果
     */
    public static <T> int countNotNullValues(T obj) {
        int count = 0;
        if (obj == null) {
            return count;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (countNotNullValues(field, obj)) {
                count++;
            }
        }
        return count;
    }

    private static boolean countNotNullValues(Field field, Object obj) {
        assert field != null && obj != null;
        try {
            //获取属性的类型(有需要，自己增加)
            return field.get(obj) != null;
        } catch (Exception e) {
            log.error(field.getName() + "获取值失败");
            log.error(Throwables.getStackTraceAsString(e));
        }
        return false;
    }
    /**
     * 将List中的对象拷贝到目标对象的List中(标准Bean)
     *
     * @param sourceList Collection<T>
     * @param targetCls 目标对象类型
     * @param <T> 源类型
     * @param <R> 目标类型
     * @return 目标类型List数组
     */
    public static <T, R> List<R> copyList(Collection<T> sourceList, Class<R> targetCls) {
        if (CollectionUtil.isNullOrEmpty(sourceList)) {
            return new ArrayList<>();
        }
        Assert.notNull(targetCls, "target class can not null");
        List<R> targetList = new ArrayList<>(sourceList.size());
        for (T source : sourceList) {
            targetList.add(beanCopyProperties(source, targetCls));
        }
        return targetList;
    }
    /**
     * 将List中的对象拷贝到目标对象的List中(标准Bean)
     *
     * @param sourceList 源List<T>
     * @param targetCls 目标对象类型
     * @param <T> 源类型
     * @param <R> 目标类型
     * @return 目标类型List数组
     */
    public static <T, R> List<R> beanCopyPropertiesForList(List<T> sourceList, Class<R> targetCls) {
        if (sourceList == null || sourceList.isEmpty() || targetCls == null) {
            return new ArrayList<>();
        }
        Assert.notNull(targetCls, "target class can not null");
        List<R> targetList = new ArrayList<>(sourceList.size());
        for (T source : sourceList) {
            targetList.add(beanCopyProperties(source, targetCls));
        }
        return targetList;
    }



    /**
     * 属性值拷贝(标准Bean)
     *
     * @param source 源对象
     * @param targetCls 目标对象类
     * @Return 拷贝目标类的实体
     */
    public static <R> R beanCopyProperties(Object source, Class<R> targetCls) {
        try {
            if (source == null) {
                return null;
            }
            Assert.notNull(targetCls, "target class can not null");
            R target = targetCls.getDeclaredConstructor().newInstance();
            BeanUtils.copyProperties(source, target);
            return target;
        } catch (Exception e) {
            log.error(Throwables.getStackTraceAsString(e));
            return null;
        }
    }

    /**
     * 属性值拷贝(标准Bean)
     *
     * @param source 源对象
     * @param target 目标对象
     * @Return 拷贝目标类的实体
     */
//    public static <R> R copyProperties(Object source, R target) {
//        try {
//            if (source == null) {
//                return null;
//            }
//            Assert.notNull(target, "target class can not null");
//            BeanUtils.copyProperties(source, target);
//            return target;
//        } catch (Exception e) {
//            log.error(Throwables.getStackTraceAsString(e));
//            return null;
//        }
//    }

    /**
     * 属性值拷贝(标准Bean)
     *
     * @param source 源对象
     * @param targetCls 目标对象类
     * @return 拷贝目标类的实体
     */
    public static <R> R beanCopyProperties(Object source, Class<R> targetCls,
        String ignoreProperties) {
        try {
            if (source == null) {
                return null;
            }
            Assert.notNull(targetCls, "target class can not null");
            R target = targetCls.getDeclaredConstructor().newInstance();

            if (StringUtil.isEmpty(ignoreProperties)) {
                BeanUtils.copyProperties(source, target);
            } else {
                BeanUtils.copyProperties(source, target, ignoreProperties.split(","));
            }
            return target;
        } catch (Exception e) {
            log.error(Throwables.getStackTraceAsString(e));
            return null;
        }
    }


    /**
     * bean 对象拷贝
     *
     */
    public static <R> R beanCopyProperties(Object source, Class<R> target, R defVal) {
        return beanCopyProperties(source, target, defVal, null);
    }

    /**
     * bean 对象拷贝
     *
     * @param source 原始对象
     * @param target 目标对象类
     * @param defVal 默认值生成器: 原始对象为null, 返回该值
     * @param ignoreProperties 拷贝时忽略字段名, 忽略多个字段使用逗号分割
     * @param <R> 目标类
     * @return 生成实体
     */
    public static <R> R beanCopyProperties(Object source, Class<R> target, R defVal,
        String ignoreProperties) {
        if (source == null) {
            return defVal;
        }

        return beanCopyProperties(source, target, ignoreProperties);
    }

    /**
     * 属性值拷贝(标准Bean)
     *
     * @param source 源对象
     * @param target 目标对象
     * @return 拷贝目标类的实体
     */
    public static void beanCopyProperties(Object source, Object target, String ignoreProperties) {
        try {
            Assert.notNull(target, "target can not null");
            if (source == null) {
                return;
            }
            if (StringUtil.isEmpty(ignoreProperties)) {
                BeanUtils.copyProperties(source, target);
            } else {
                BeanUtils.copyProperties(source, target, ignoreProperties.split(","));
            }
        } catch (Exception e) {
            log.error(Throwables.getStackTraceAsString(e));
        }
    }

    public static final int COVER_EXISTS_VALUE=1;

    public static final int MERGE_NULL_VALUE=2;

    /**
     * 合并source中非null的字段到target
     * @param source
     * @param target
     * @param  option 二进制(第一位表示是否覆盖target已有值 0：不覆盖 1：覆盖，第二位表示是否合并source中null字段到target，0：不合并 ，1：合并)<br>
     * 即<br>0不合并null 不覆盖已有值,
     *      1不合并null 覆盖已有值,
     *      2合并null 不覆盖已有值,
     *      3合并null 覆盖已有值
     * @param ignoreProperties
     */
    public static void mergeProperties(Object source, Object target,int option, String ignoreProperties){
        try {
            Assert.notNull(target, "target can not null");
            if (source == null) {
                return;
            }

            Class<?> actualEditable = target.getClass();
            PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);

            Set<String> ignoreList;
            if (ignoreProperties != null) {
                String[] propertiesArray = ignoreProperties.split(",");
                ignoreList = propertiesArray.length > 0 ? new HashSet<>(Arrays.asList(propertiesArray)) : Collections.emptySet();
            } else {
                ignoreList = null;
            }

            for (PropertyDescriptor targetPd : targetPds) {
                Method writeMethod = targetPd.getWriteMethod();
                if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                    PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                    if (sourcePd != null) {
                        Method readMethod = sourcePd.getReadMethod();
                        if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                            try {
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    readMethod.setAccessible(true);
                                }
                                Object value = readMethod.invoke(source);
                                //设置为不合并null值 且 source 的值为null 时 跳过
                                if((MERGE_NULL_VALUE&option)!=MERGE_NULL_VALUE &&value==null){
                                    continue;
                                }
                                Method targetReadMethod = targetPd.getReadMethod();
                                if (!Modifier.isPublic(targetReadMethod.getDeclaringClass().getModifiers())) {
                                    targetReadMethod.setAccessible(true);
                                }
                                //设置为不覆盖target已存在值 且已存在值不为null 时 跳过
                                if((COVER_EXISTS_VALUE&option)!=COVER_EXISTS_VALUE&& targetReadMethod.invoke(target) !=null){
                                    continue;
                                }

                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                writeMethod.invoke(target, value);
                            }
                            catch (Throwable ex) {
                                throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error(Throwables.getStackTraceAsString(e));
        }
    }

    public static void beanCopyWithProperties(Object source, Object target, String... copyProperties){
        try {
            Assert.notNull(target, "target can not null");
            if (source == null) {
                return;
            }

            Class<?> actualEditable = target.getClass();
            PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
            Set<String> copyList;
            if (copyProperties.length > 0) {
                copyList = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(copyProperties)));
            } else {
                copyList = null;
            }


            for (PropertyDescriptor targetPd : targetPds) {
                Method writeMethod = targetPd.getWriteMethod();
                if (writeMethod != null && (copyList == null || copyList.contains(targetPd.getName()))) {
                    PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                    if (sourcePd != null) {
                        Method readMethod = sourcePd.getReadMethod();
                        if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                            try {
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    readMethod.setAccessible(true);
                                }
                                Object value = readMethod.invoke(source);
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                writeMethod.invoke(target, value);
                            }
                            catch (Throwable ex) {
                                throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error(Throwables.getStackTraceAsString(e));
        }
    }

    /**
     * Get value or default value if it is null.
     *
     * @param value 原值
     * @param defaultValue 默认值
     * @param <T> 类型
     * @return 结果
     */
    public static <T> T valueOrDefault(T value, T defaultValue) {
        return value == null ? defaultValue : value;
    }


    /**
     * Return the named getter method on the bean or null if not found.
     *
     * @return the named getter method
     */
    private static Method getMethod(Object bean, String propertyName) {
        StringBuilder sb = new StringBuilder("get")
            .append(Character.toUpperCase(propertyName.charAt(0)));
        if (propertyName.length() > 1) {
            sb.append(propertyName.substring(1));
        }
        String getterName = sb.toString();
        for (Method m : bean.getClass().getMethods()) {
            if (getterName.equals(m.getName()) && m.getParameterTypes().length == 0) {
                return m;
            }
        }
        return null;
    }

    /**
     * Return the named field on the bean or null if not found.
     *
     * @return the named field
     */
    private static Field getField(Object bean, String propertyName) {
        for (Field f : bean.getClass().getDeclaredFields()) {
            if (propertyName.equals(f.getName())) {
                return f;
            }
        }
        return null;
    }

    private static void validateArgs(Object bean, String propertyName) {
        if (bean == null) {
            throw new IllegalArgumentException("bean is null");
        }
        if (propertyName == null) {
            throw new IllegalArgumentException("propertyName is null");
        }
        if (propertyName.trim().length() == 0) {
            throw new IllegalArgumentException("propertyName is empty");
        }
    }

    /**
     * Retrieve a named bean property value.
     *
     * @param bean bean
     * @return the property value
     */
    public static Object getBeanProperty(Object bean, String propertyName) {
        validateArgs(bean, propertyName);

        // try getters first
        Method getter = getMethod(bean, propertyName);
        if (getter != null) {
            try {
                return getter.invoke(bean);
            } catch (Exception ignored) {
            }
        }

        Field field = getField(bean, propertyName);
        if (field != null) {
            try {
                field.setAccessible(true);
                return field.get(bean);
            } catch (Exception ignored) {
            }
        }

        return null;
    }

    /**
     * Retrieve a Long bean property value.
     *
     * @param bean bean
     * @return long value
     */
    public static long getLongBeanProperty(final Object bean, final String propertyName)
        throws NoSuchFieldException {
        validateArgs(bean, propertyName);
        Object o = getBeanProperty(bean, propertyName);
        if (o == null) {
            throw new NoSuchFieldException(propertyName);
        } else if (!(o instanceof Number)) {
            throw new IllegalArgumentException(propertyName + " not an Number");
        }
        return ((Number) o).longValue();
    }

    public static <R> R mapToClass(Map<String, ?> params, Class<R> targetCls) {
        Assert.notEmpty(params, "params can not empty");
        try {
            return JSON.parseObject(JSON.toJSONString(params), targetCls);
        } catch (Exception e) {
            log.error(Throwables.getStackTraceAsString(e));
            return null;
        }
    }

    public static <T> void setProperty(T bean, String name, String value) {
        Class clazz = bean.getClass();
        Field field = ReflectionUtils.findField(clazz, name);
        if (field == null) {
            return;
        }
        char[] cs = name.toCharArray();
        cs[0] -= 32;
        final Method method = ReflectionUtils
            .findMethod(clazz, "set" + String.valueOf(cs), field.getType());
        field.getType().getSimpleName();
        Object object = null;
        switch (field.getGenericType().getTypeName()) {
            case "int":
                object = Integer.parseInt(value);
                break;
            case "java.lang.Integer":
                object = Integer.valueOf(value);
                break;
            case "boolean":
                object = Boolean.parseBoolean(value);
                break;
            case "java.lang.Boolean":
                if (StringUtil.stringEquals(value,"1")) {
                    object = Boolean.TRUE;
                    break;
                }
                object = Boolean.valueOf(value);
                break;
            case "long":
                object = Long.parseLong(value);
                break;
            case "java.lang.Long":
                object = Long.valueOf(value);
                break;
            case "java.util.List<java.lang.String>":
                object = split(value);
                break;
            case "java.util.List<java.lang.Long>":
                object = split(value).stream().map(Long::valueOf).collect(Collectors.toList());
                break;
            case "java.util.List<java.lang.Integer>":
                object = split(value).stream().map(Integer::valueOf).collect(Collectors.toList());
                break;
            case "java.util.List<java.lang.Boolean>":
                object = split(value).stream().map(Boolean::valueOf).collect(Collectors.toList());
                break;
            case "java.util.Date":
                //yyyy-MM-dd hh:mm:ss
                String regex1 = "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}";
                Pattern pattern1 = Pattern.compile(regex1);
                Matcher m1 = pattern1.matcher(value);
                if (m1.matches()) {
                    object = DateUtil.parseDateTime(value);
                    break;
                }
                //yyymmdd
                String regex2 = "[0-9]{4}-[0-9]{2}-[0-9]{2}";
                Pattern pattern2 = Pattern.compile(regex2);
                Matcher m2 = pattern2.matcher(value);
                if (m2.matches()) {
                    object = DateUtil.parseDate(value);
                    break;
                }
//                //13位
//                String regex3 = "^\\d{13}$";
//                Pattern pattern3 = Pattern.compile(regex3);
//                Matcher m3 = pattern3.matcher(value);
//                if (m3.matches()) {
//                    object = new Date(Long.parseLong(value));
//                    break;
//                }
//                //10位
//                String regex4 = "^\\d{10}$";
//                Pattern pattern4 = Pattern.compile(regex4);
//                Matcher m4 = pattern4.matcher(value);
//                if (m4.matches()) {
//                    object = new Date(Long.parseLong(value + "000"));
//                    break;
//                }
                String regex3 = "^\\d+$";
                Pattern pattern3 = Pattern.compile(regex3);
                Matcher m3 = pattern3.matcher(value);
                if (m3.matches()) {
                    object = new Date(Long.parseLong(value));
                    break;
                }
                break;
            case "java.time.LocalDate":
                object = LocalDate.parse(value);
                break;
            case "java.time.LocalTime":
                object = LocalTime.parse(value);
                break;
            case "java.time.LocalDateTime":
                object = LocalDateTime.parse(value.replace(" ", "T"));
                break;
            case "java.math.BigDecimal":
                //如果传了，自动解析掉
                object = new BigDecimal(value.replace(",", ""));
                break;
            default:
                object = value;

        }
        assert method != null;
        try {
            method.invoke(bean, object);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static List<String> split(String value) {
        if (StringUtil.isEmpty(value)) {
            return new ArrayList<>();
        }
        if (value.startsWith("[") && value.endsWith("]")) {
            value = value.substring(1, value.length() - 1);//support [1,2,3]
        }
        return Lists.newArrayList(Splitter.on(",").omitEmptyStrings().trimResults().split(value));
    }
}